package context_util

import (
	"fmt"
	"net/http"
	"strings"

	"gitee.com/Himan000/otel_zero/otel_zap_logger/otel"
	zero "gitee.com/Himan000/otel_zero/zero_mdc_log"
	zero_config "gitee.com/Himan000/otel_zero/zero_mdc_log/config"
	zero_log "gitee.com/Himan000/otel_zero/zero_mdc_log/log"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/spf13/viper"
)

const (
	LOG_CONTEXT_PREFIX = "logcontext-"
	REQUEST_IP         = LOG_CONTEXT_PREFIX + "requestip" // 全局变量
	REQUEST_ID         = LOG_CONTEXT_PREFIX + "requestid" // 全局变量
	USER_ID            = LOG_CONTEXT_PREFIX + "userid"    // 全局变量
	SESSION_ID         = LOG_CONTEXT_PREFIX + "sessionid" // 当前服务变量
	APP_ID             = LOG_CONTEXT_PREFIX + "appid"     // 当前服务变量
	ENV_TYPE           = LOG_CONTEXT_PREFIX + "envtype"   // 当前服务变量
)

/*
一些不需要贯穿整个调用链的日志字段
*/
func getLocalLogContextMap() map[string]int {
	m := map[string]int{
		// SESSION_ID: 1,
		APP_ID:   1,
		ENV_TYPE: 1,
	}

	return m
}

/*
所有已知的日志字段
*/
func getAllLogContextMap() map[string]int {
	m := map[string]int{
		REQUEST_IP: 1,
		REQUEST_ID: 1,
		USER_ID:    1,
		SESSION_ID: 1,
		APP_ID:     1,
		ENV_TYPE:   1,
	}

	return m
}

/*
判断是否为当前服务的日志字段。
*/
func isLocalLogContext(field string) bool {
	m := getLocalLogContextMap()
	_, ok := m[field]
	return ok
}

/*
判断是否为已知的日志字段。
*/
func isKnownLogContext(field string) bool {
	m := getAllLogContextMap()
	_, ok := m[field]
	return ok
}

/*
除去LOG_CONTEXT_PREFIX后的字段名，用于设置MDC及打印日志。即在header设置的是有LOG_CONTEXT_PREFIX的，
但MDC是没有LOG_CONTEXT_PREFIX的。
*/
func getLogContextNameWithoutPrefix(fieldName string) string {
	name := fieldName

	if strings.HasPrefix(name, LOG_CONTEXT_PREFIX) {
		name = fieldName[len(LOG_CONTEXT_PREFIX):]
	}

	return name
}

func getLogContextNameWithPrefix(fieldName string) string {
	name := fieldName

	if !strings.HasPrefix(name, LOG_CONTEXT_PREFIX) {
		name = LOG_CONTEXT_PREFIX + fieldName
	}

	return name
}

func lowerField(field string) string {
	return strings.ToLower(field)
}

/*
将请求header中的日志字段设置到MDC
*/
func SetMdcFromHeaderMiddleware(c *gin.Context) {
	ctx := otel.Default.Start(c.Request.Context())
	zero_log.MDC().Set("ctx", ctx)                              // 将ctx房到MDC，后面request请求可以用
	zero_log.MDC().Set(zero_log.TRACE_ID, otel.GetTraceId(ctx)) // 这个是调用链的trace_id
	zero_log.MDC().Set(zero_log.APP_ID, getAppId())             // 设置到MDC但不传入下一个请求
	zero_log.MDC().Set(zero_log.ENV_TYPE, getEnvType())
	zero_log.MDC().Set(zero_log.SESSION_ID, getSessionId(c))
	setMdcFromHeaderOperation(c, REQUEST_IP, setReqeustIpFromHeader)
	setMdcFromHeaderOperation(c, REQUEST_ID, setRequestIdFromHeader)
	setMdcFromHeaderOperation(c, USER_ID, setUserIdFromHeader)

	for key := range c.Request.Header {
		key = lowerField(key)

		if strings.HasPrefix(key, LOG_CONTEXT_PREFIX) && !isKnownLogContext(key) {
			setMdcFromHeaderOperation(c, key, setCommonFieldFromHeader)
		}
	}

	c.Next()
	otel.End()
}

func getAppId() string {
	return viper.GetString(zero_config.APP_ID)
}

func getEnvType() string {
	return viper.GetString(zero_config.ENV_TYPE)
}

func getSessionId(c *gin.Context) string {
	sessionIdKey := viper.GetString(zero_config.SESSION_ID_KEY)
	sessionID := c.GetHeader(sessionIdKey)

	if sessionID == "" {
		sessionID = "nosessionid"
		c.Header(sessionIdKey, sessionID)
	}

	return sessionID
}

// 作为参数的函数
func setMdcFromHeaderOperation(c *gin.Context, fieldName string, operation func(*gin.Context, string)) {
	operation(c, fieldName)
}

/*
 * 获取request ip: 先从header获取REQUEST_IP_KEY的值，如果没有，说明是从Nginx过来的请求。
 */
func setReqeustIpFromHeader(c *gin.Context, fieldName string) {
	fieldWithoutPrefix := getLogContextNameWithoutPrefix(fieldName)
	fieldValue := c.GetHeader(fieldName)

	if fieldValue == "" {
		fieldValue = c.ClientIP()
	}

	zero_log.MDC().Set(fieldWithoutPrefix, fieldValue)
	zero.AddGlobalFields(fieldWithoutPrefix)
}

/*
特定的日志字段方法
*/
func setRequestIdFromHeader(c *gin.Context, fieldName string) {
	fieldWithoutPrefix := getLogContextNameWithoutPrefix(fieldName)
	fieldValue := c.GetHeader(fieldName)

	if fieldValue == "" {
		fieldValue = uuid.New().String()
	}

	zero_log.MDC().Set(fieldWithoutPrefix, fieldValue)
	zero.AddGlobalFields(fieldWithoutPrefix)
}

/*
特定的日志字段方法
*/
func setUserIdFromHeader(c *gin.Context, fieldName string) {
	fieldWithoutPrefix := getLogContextNameWithoutPrefix(fieldName)
	fieldValue := c.GetHeader(fieldName)

	if fieldValue == "" {
		if u, ok := c.Get("userid"); ok {
			fieldValue = fmt.Sprintf("%v", u)
		}
	}

	if fieldValue == "" {
		fieldValue = "nouserid"
	}

	c.Header(fieldName, fieldValue)

	zero_log.MDC().Set(fieldWithoutPrefix, fieldValue)
	zero.AddGlobalFields(fieldWithoutPrefix)
}

/*
特定的日志字段方法
*/
func setCommonFieldFromHeader(c *gin.Context, fieldName string) {
	fieldWithoutPrefix := getLogContextNameWithoutPrefix(fieldName)
	fieldValue := c.GetHeader(fieldName)
	fieldWithLower := lowerField(fieldName)

	if fieldValue == "" {
		fieldValue = viper.GetString(fieldWithLower)
	}

	zero_log.MDC().Set(fieldWithoutPrefix, fieldValue)
	zero.AddGlobalFields(fieldWithoutPrefix)
}

/*
将需要贯穿整个调用链的日志字段穿到下一个请求
*/
func SetLogContextToNewRequst(request *http.Request) {
	for _, key := range zero.GetGlobalFields() {
		if key == zero_log.TRACE_ID {
			continue
		}

		keyWithPrefix := getLogContextNameWithPrefix(key)

		if !isLocalLogContext(keyWithPrefix) {
			logContextValue, _ := zero_log.MDC().Get(key)

			if logContextValue.(string) != "" {
				request.Header.Add(keyWithPrefix, logContextValue.(string))
			}
		}
	}
}
